// 15 面向对象程序设计
/**
 * 面向对象程序设计基于三个基本概念：数据抽象、继承和动态绑定。第7章已经介绍了数据抽象的知识，本章将介绍继承和动态绑定。
 * 继承和动态绑定对程序的编写有两方面的影响：一是我们可以更容易地定义与其他类相似但不完全相同的新类；二是在使用这些彼此相似的类编写程序时，我们可以在一定程度上忽略掉它们的区别。
 * 在很多程序中都存在着一些相互关联但是有细微差别的概念。
 * 例如，书店中不同书籍的定价策略可能不同：有的书籍按原价销售，有的则打折销售。有时，我们给那些购买书籍超过一定数量的顾客打折；另一些时候，则只对前多少本销售的书籍打折，之后就调回原价，等等。
 * 面向对象的程序设计（OOP）适用于这类应用。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include "../Chapter13/13.5.cc" // 不能编译，因为重复定义的main函数
#include "../Chapter12/12.1.6.cc" // 不能编译，因为重复定义的main函数
#include <functional>
#include "../VisualStudio2012/15/Quote.h"



class Basket {
public:
    // Basket使用合成的默认构造函数和拷贝控制成员
    void add_item(const shared_ptr<Quote>& sale) {
        items.insert(sale);
    }
    // 打印每本书的总价和购物篮中所有书的总价
    double total_receipt(std::ostream&) const;

    void add_item(const Quote& sale); // 拷贝给定的对象
    void add_item(Quote&& sale);      // 移动给定的对象

    

private:
    // 该函数用于比较shared_ptr，multiset成员会用到它
    static bool compare(const shared_ptr<Quote>& lhs, const shared_ptr<Quote>& rhs) {
        return lhs->isbn() < rhs->isbn();
    }
    // multiset保存多个报价，按照compare成员排序
    std::multiset<shared_ptr<Quote>, decltype(&compare)> items { compare }; // 有关decltype(&compare)的疑问，参见6.7 函数指针
    // 使用一个multiset（参见11.2.1节，第377页）来存放交易信息，这样我们就能保存同一本书的多条交易记录，而且对于一本给定的书籍，它的所有交易信息都保存在一起（参见11.2.2节，第378页）。
};
double Basket::total_receipt(std::ostream& os) const {
    double sum = 0; // 保存实时计算出的总价
    // iter指向ISBN相同的一批元素中的第一个
    // upper_bound返回一个迭代器，该迭代器指向这批元素的尾后位置
    /** 
     * 在C++标准模板库（STL）中，std::multiset是一种关联容器，它存储元素的顺序是基于元素的值。对于std::multiset，可以有多个元素具有相同的值。
upper_bound()函数是关联容器（如std::set和std::multiset）提供的成员函数之一，它用于查找第一个大于等于给定值的元素的位置。请注意，由于std::multiset允许存在相同值的元素，所以这个位置可能不是唯一的。
具体来说，upper_bound()返回一个迭代器，指向满足以下条件的第一个元素：
1、元素的键值大于传递给upper_bound()的键值。
2、如果没有这样的元素，则返回尾后迭代器，表示集合结束。
如果要找到与给定值相等的所有元素的范围，通常会使用lower_bound()和upper_bound()这两个函数。
    */
    for (auto iter = items.cbegin(); iter!= items.cend(); iter=items.upper_bound(*iter)) {
        sum += print_total(os, **iter,items.count(*iter));
    }
    os << "total due: " << sum << endl;
    return sum;
}
void Basket::add_item(const Quote& sale) { // 注意，这里是拷贝
    items.insert(make_shared<Quote>(sale.clone()));
}
void Basket::add_item(Quote&& sale) { // 注意，这里是移动
    items.insert(make_shared<Quote>(std::move(sale).clone()));
    // 在右值版本中，尽管sale的类型是右值引用类型，但实际上sale本身（和任何其他变量一样）是个左值（参见13.6.1节，第471页）。因此，我们调用move把一个右值引用绑定到sale上。
    /**
     * 在C++中，void add_item(Quote&& sale)中的形参sale是一个右值引用。这表示这个函数接受一个右值作为参数，并且可以通过sale来修改传递给它的对象。
然而，尽管sale本身是一个右值引用，但当你通过它来访问实际的对象时，你得到的实际上是左值。
这是因为右值引用是绑定到右值（通常是临时对象）上的引用，而这个右值仍然是一个有名字的、可以取地址的实体，因此符合左值的定义。
换句话说，虽然你不能直接对右值引用进行取址操作，但你可以通过它来间接地访问和修改底层的左值。
这就是所谓的“转发引用”，它允许你将一个右值有效地转换为左值以便进行进一步的操作，如移动构造函数或移动赋值运算符。
所以，在这个上下文中，当提到sale的时候，它是作为一个右值引用，但当你通过它来访问底层的对象时，那个对象是一个左值。
    */
}

int main()
{
    // 15.8 容器与继承
    // 当我们使用容器存放继承体系中的对象时，通常必须采取间接存储的方式。因为不允许在容器中保存不同类型的元素，所以我们不能把具有继承关系的多种类型的对象直接存放在容器当中。
    // 当派生类对象被赋值给基类对象时，其中的派生类部分将被“切掉”，因此容器和存在继承关系的类型无法兼容。

    // 在容器中放置（智能）指针而非对象
    // 当我们希望在容器中存放具有继承关系的对象时，我们实际上存放的通常是基类的指针（更好的选择是智能指针（参见12.1节，第400页））。
    vector<shared_ptr<Quote>> basket;
    //basket.push_back(make_shared<Quote>("aaa", 50));
    //basket.push_back(make_shared<Bulk_quote>("bbb", 50, 10, .25));
    // 正如我们可以将一个派生类的普通指针转换成基类指针一样（参见15.2.2节，第530页），我们也能把一个派生类的智能指针转换成基类的智能指针。
    
    // 15.8.1 编写Basket类
    // 对于C++面向对象的编程来说，一个悖论是我们无法直接使用对象进行面向对象编程。相反，我们必须使用指针和引用。因为指针会增加程序的复杂性，所以我们经常定义一些辅助的类来处理这种复杂情况。

    // 定义Basket的成员

    // 隐藏指针
    // Basket的用户仍然必须处理动态内存，原因是add_item需要接受一个shared_ptr参数。因此，用户不得不按照如下形式编写代码：
    Basket bsk;
    bsk.add_item(make_shared<Quote>("aaa", 45));
    bsk.add_item(make_shared<Bulk_quote>("bbb", 45, 3, .15));
    // 我们的下一步是重新定义add_item，使得它接受一个Quote对象而非shared_ptr。新版本的add_item将负责处理内存分配，这样它的用户就不必再受困于此了。
    // 唯一的问题是add_item不知道要分配的类型。

    // 模拟虚拷贝
    // 为了解决上述问题，我们给Quote类添加一个虚函数，该函数将申请一份当前对象的拷贝。继承自Quote的Bulk_quote也定义类似该虚函数的函数（将Quote换成Bulk_quote）。
    //   virtual Quote* clone() const & { return new Quote(*this); }
    //   virtual Quote* clone() && { return new Quote(std::move(*this)); }
    // 因为我们拥有add_item的拷贝和移动版本，所以我们分别定义clone的左值和右值版本（参见13.6.3节，第483页）。
    // 每个clone函数分配当前类型的一个新对象，其中，const左值引用成员将它自己拷贝给新分配的对象；右值引用成员则将自己移动到新数据中。
    // 我们可以使用clone很容易地写出新版本的add_item：
    // 


    return 0;
}